package com.zhengxing.jienci.user.service.rbac.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.base.CaseFormat;
import com.google.common.collect.Lists;
import com.zhengxing.jienci.user.domain.rbac.Resource;
import com.zhengxing.jienci.user.domain.rbac.ResourceFunc;
import com.zhengxing.jienci.common.util.IdGenerateUtil;
import com.zhengxing.jienci.user.mapper.rbac.ResourceMapper;
import com.zhengxing.jienci.user.service.rbac.ResourceFuncService;
import com.zhengxing.jienci.user.service.rbac.ResourceService;
import com.zhengxing.jienci.user.service.rbac.RoleResourceFuncService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * ResourceServiceImpl
 *
 * @author philbert
 * @version 1.00
 */
@Slf4j
@Service
@Transactional
@Lazy
public class ResourceServiceImpl extends ServiceImpl<ResourceMapper, Resource> implements ResourceService {

    public static final String REDIS_KEY_PREFIX_FIND_RESOURCES_BY_USER_ID = "sys.resource::findAllResourcesByUserId:";

    @Autowired
    private ResourceMapper resourceMapper;
    @Autowired
    private RoleResourceFuncService roleResourceFuncService;
    @Autowired
    private ResourceFuncService resourceFuncService;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 保存资源
     *
     * @param resource 资源
     * @return true=成功；false=失败
     */
    @Override
    public boolean save(Resource resource) {
        if (StringUtils.isBlank(resource.getId())) resource.setId(IdGenerateUtil.getId());
        if (resource.getCreatedAt() == null) resource.setCreatedAt(System.currentTimeMillis());

        boolean bool = super.retBool(resourceMapper.insert(resource));

        if (bool) {
            // 删除父资源的功能（不包括列表功能）
            ArrayList<String> resourceIds = Lists.newArrayList(resource.getParentId());
            resourceFuncService.deleteByResourceIds(resourceIds, false);
            roleResourceFuncService.deleteByResourceIds(resourceIds);
            // 新增该资源的列表功能
            ResourceFunc resourceFunc = new ResourceFunc();
            resourceFunc.setCode("list");
            resourceFunc.setName("列表");
            resourceFunc.setRemark("列表");
            resourceFunc.setResourceId(resource.getId());
            resourceFunc.setSortNo(0);
            resourceFuncService.save(resourceFunc);

            Set<String> keySet = redisTemplate.keys(REDIS_KEY_PREFIX_FIND_RESOURCES_BY_USER_ID + "*");
            if (keySet != null) redisTemplate.delete(keySet);
        }

        return bool;
    }

    /**
     * 删除资源
     *
     * @param id 主键
     * @return true=成功；false=失败
     */
    @Override
    public boolean delete(String id) {
        List<Resource> resources = Lists.newArrayList();
        resources.add(resourceMapper.selectById(id));
        this.findAllChildren(resources, id);
        List<String> resourceIds = Lists.newArrayList();
        resources.forEach(resource -> resourceIds.add(resource.getId()));

        resourceFuncService.deleteByResourceIds(resourceIds, true);
        roleResourceFuncService.deleteByResourceIds(resourceIds);

        LambdaUpdateWrapper<Resource> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        lambdaUpdateWrapper.in(Resource::getId, resourceIds);
        boolean bool = super.retBool(resourceMapper.delete(lambdaUpdateWrapper));

        if (bool) {
            Set<String> keySet = redisTemplate.keys(REDIS_KEY_PREFIX_FIND_RESOURCES_BY_USER_ID + "*");
            if (keySet != null) redisTemplate.delete(keySet);
        }

        return bool;
    }

    /**
     * 更新资源
     *
     * @param resource 资源
     * @return true=成功；false=失败
     */
    @Override
    public boolean update(Resource resource) {
        if (resource.getCreatedAt() == null) resource.setUpdatedAt(System.currentTimeMillis());
        boolean bool = super.retBool(resourceMapper.updateById(resource));

        if (bool) {
            Set<String> keySet = redisTemplate.keys(REDIS_KEY_PREFIX_FIND_RESOURCES_BY_USER_ID + "*");
            if (keySet != null) redisTemplate.delete(keySet);
        }

        return bool;
    }

    /**
     * 查询资源
     *
     * @param id 主键
     * @return 资源
     */
    @Override
    @Transactional(readOnly = true)
    public Resource get(String id) {
        return resourceMapper.selectById(id);
    }

    /**
     * 查询资源分页对象
     *
     * @param paramMap 参数Map
     * @param orderBy  排序方式
     * @param pageNo   页码
     * @param pageSize 每页最大数量
     * @return 资源分页对象
     */
    @Override
    @Transactional(readOnly = true)
    public IPage<Resource> getPagination(Map<String, Object> paramMap, String orderBy, int pageNo, int pageSize) {
        String parentId = MapUtils.getString(paramMap, "parentId");
        String name = MapUtils.getString(paramMap, "name");
        String code = MapUtils.getString(paramMap, "code");

        QueryWrapper<Resource> queryWrapper = new QueryWrapper<>();
        if (StringUtils.isNotBlank(orderBy)) {
            String[] array = orderBy.split(" ");
            String format = "CONVERT(%s USING gbk)";
            String field = String.format(format, CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, array[0]));
            if (StringUtils.equalsIgnoreCase(array[1], "asc")) {
                queryWrapper.orderByAsc(field);
            } else if (StringUtils.equalsIgnoreCase(array[1], "desc")) {
                queryWrapper.orderByDesc(field);
            } else {
                throw new RuntimeException();
            }
        }
        LambdaQueryWrapper<Resource> lambdaQueryWrapper = queryWrapper.lambda();
        lambdaQueryWrapper.eq(Resource::getParentId, parentId);
        if (StringUtils.isNotBlank(name)) lambdaQueryWrapper.and(obj -> obj.like(Resource::getName, name));
        if (StringUtils.isNotBlank(code)) lambdaQueryWrapper.and(obj -> obj.like(Resource::getCode, code));

        Page<Resource> page = new Page<>(pageNo, pageSize);
        resourceMapper.selectPage(page, queryWrapper);
        page.getRecords().parallelStream().forEach(resource ->
                resource.setChildren(this.findChildren(resource.getId()))
        );

        return page;
    }

    /**
     * 查询代码是否已存在
     *
     * @param code     代码
     * @param excludId 要排除的记录主键
     * @return true=已存在；false=不存在
     */
    @Override
    @Transactional(readOnly = true)
    public boolean codeExist(String code, String excludId) {
        return resourceMapper.codeExist(code, excludId) != null;
    }

    /**
     * 通过主键查询该资源下的子资源，并封装成树节点的数据结构
     *
     * @param id 主键
     * @return 该资源的子资源的树节点
     */
    @Override
    @Transactional(readOnly = true)
    public List<Map<String, Object>> findChildrenTreeNodes(String id) {
        List<Map<String, Object>> resourceMaps = new ArrayList<>();
        if (StringUtils.isNotBlank(id)) {
            List<Resource> resources = this.findChildren(id);
            resources.forEach(tmpResource -> {
                Map<String, Object> tmpMap = new HashMap<>();
                tmpMap.put("id", tmpResource.getId());
                tmpMap.put("name", tmpResource.getName());
                tmpMap.put("isParent", !this.findChildren(tmpResource.getId()).isEmpty());
                resourceMaps.add(tmpMap);
            });
        }

        return resourceMaps;
    }

    /**
     * 查询所有资源及资源功能，并封装成树节点的数据结构
     *
     * @return 所有资源的树节点
     */
    @Override
    @Transactional(readOnly = true)
    public List<Map<String, Object>> findAllTreeNodesWithFuncs() {
        List<Resource> resources = this.findChildren("0");
        this.findAllChildren(null, resources);

        List<Map<String, Object>> resourceMaps = new ArrayList<>();
        addResourceFuncs(resources, resourceMaps);

        return resourceMaps;
    }

    @Transactional(readOnly = true)
    public List<Resource> findAllByUserId(String userId) {
        String redisKey = REDIS_KEY_PREFIX_FIND_RESOURCES_BY_USER_ID + userId;
        ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
        List<Resource> resources = (List<Resource>) valueOperations.get(redisKey);
        if (resources == null || resources.isEmpty()) {
            resources = findChildren(userId, "0");
            findAllChildren(userId, resources);
            valueOperations.setIfAbsent(redisKey, resources, 30, TimeUnit.MINUTES);
        }
        return resources;
    }

    /**
     * 通过主键查询该资源下的所有子资源
     *
     * @param resources 存储所有子资源的集合
     * @param id        主键
     */
    private void findAllChildren(List<Resource> resources, String id) {
        List<Resource> children = this.findChildren(id);
        if (!children.isEmpty()) {
            resources.addAll(children);
            children.forEach(resource -> findAllChildren(resources, resource.getId()));
        }
    }

    /**
     * 递归查询所有子资源List。用户主键=空或null，所有子资源；用户主键!=空或null，该用户有权限的所有子资源
     *
     * @param userId    用户主键
     * @param resources 子资源List
     */
    private void findAllChildren(String userId, List<Resource> resources) {
        resources.forEach(resource -> {
            String id = resource.getId();
            List<Resource> children = this.findChildren(userId, id);
            if (children != null && !children.isEmpty()) {
                findAllChildren(userId, children);
                resource.setChildren(children);
            }
        });
    }

    /**
     * 通过主键查询该资源下的子资源
     *
     * @param id 主键
     * @return 该资源下的子资源
     */
    private List<Resource> findChildren(String id) {
        LambdaQueryWrapper<Resource> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Resource::getParentId, id).orderByAsc(Resource::getSortNo);
        lambdaQueryWrapper.orderByAsc(Resource::getCode);

        return resourceMapper.selectList(lambdaQueryWrapper);
    }

    /**
     * 根据用户名称和父ID查询子资源List。用户名称=空或null，返回该父ID的子资源；用户名称!=空或null，返回该用户有权限的该父ID的子资源
     *
     * @param userId   用户ID
     * @param parentId 父ID
     * @return 子资源List
     */
    private List<Resource> findChildren(String userId, String parentId) {
        if (StringUtils.isBlank(userId)) {
            return this.findChildren(parentId);
        }

        return resourceMapper.findByUserIdAndParentId(userId, parentId);
    }

    private void addResourceFuncs(List<Resource> resources, List<Map<String, Object>> resourceMaps) {
        resources.forEach(tmpResource -> {
            final String resourceId = tmpResource.getId();
            Map<String, Object> map = new HashMap<>();
            map.put("id", resourceId);
            map.put("name", tmpResource.getName());
            map.put("pId", tmpResource.getParentId());
            map.put("isParent", true);
            map.put("nocheck", true);
            List<Resource> children = tmpResource.getChildren();
            if (children.isEmpty()) {
                List<ResourceFunc> resourceFuncs = resourceFuncService.findByResourceId(resourceId);
                resourceFuncs.forEach(resourceFunc ->
                        resourceMaps.add(new HashMap<String, Object>() {{
                            put("id", resourceFunc.getId());
                            put("name", resourceFunc.getName());
                            put("pId", resourceId);
                            put("isParent", false);
                        }})
                );
            } else {
                addResourceFuncs(children, resourceMaps);
            }

            resourceMaps.add(map);
        });
    }

}
